package vh.android.multiconnector.library;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import vh.android.multiconnector.library.broadcast.BroadcastReceiver.PacketListener;
import vh.android.multiconnector.library.broadcast.Broadcaster;
import vh.android.multiconnector.library.broadcast.PingBroadcaster;
import vh.android.multiconnector.library.model.Channel;
import vh.android.multiconnector.library.model.ChannelListener;
import vh.android.multiconnector.library.model.ServerListener;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class ChannelManager extends PingBroadcaster implements PacketListener,
		ServerListener {

	/**
	 * Some constats
	 */
	private static final String LOG_TAG = ChannelManager.class.getName();
	public static final String IA_CHANNEL = Broadcaster.INTENT_ACTION
			+ "_CHANNEL";
	private static final String IE_PORT = "PORT";
	private static final String IE_IP = "IP";
	private static final String IE_NAME = "NAME";
	private static final long CHANNEL_TIMEOUT_CHECK = 3000L;

	/**
	 * Global variables
	 */
	private PacketListener mHandler;
	private Server mServer;
	private String mChannelName;
	private ChannelListener mListener;
	private HashMap<String, Channel> mChannels = new HashMap<String, Channel>();
	private HashMap<Channel, Client> mClients = new HashMap<Channel, Client>();

	public ChannelManager(Context pContext, int pBroadCastPort,
			String pApplicationTag) throws IOException {
		super(pContext, pBroadCastPort, pApplicationTag);

		setOnReceiveHandler(this);

		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {
				// check which channels timedout
				synchronized (mChannels) {
					ArrayList<String> remove = new ArrayList<String>();
					for (Channel c : mChannels.values()) {
						if (c.isTimedout())
							remove.add(c.getChecksum());
					}

					for (String checksum : remove) {
						Channel channel = mChannels.get(checksum);
						mChannels.remove(checksum);
						Log.e(LOG_TAG, channel.getName() + " timedout.");
						if (mListener != null)
							mListener.onChannelTimedout(channel);
					}
				}
			}
		}, 0L, CHANNEL_TIMEOUT_CHECK);
	}

	public void createChannel(String pChannelName) {
		if (mServer != null) {
			Log.e(LOG_TAG,
					"Server already started. Close the old one before creating a new one.");
			return;
		}
		mChannelName = pChannelName;
		mServer = new Server(this);
		// start looking for a port and start server
		// TODO implement listeners
		mServer.start();
	}

	public void joinChannel(Channel pChannel) {
		synchronized (mChannels) {
			// channel not listed or timedout yet
			// TODO throw error
			if (!mChannels.containsKey(pChannel.getChecksum())) {
				Log.e(LOG_TAG, "Channel not found.");
				return;
			}
		}
		synchronized (mClients) {
			// channel not listed or timedout yet
			// TODO throw error
			if (mClients.containsKey(pChannel)) {
				Log.e(LOG_TAG, "Channel already joined.");
				return;
			}
		}

		// start new client - connect to server
		Client client = new Client(pChannel.getChecksum(), pChannel.getIP(), pChannel.getPort());
		client.start();
		mClients.put(pChannel, client);
	}

	/**
	 * @return <code>null</code> when no server has started yet.
	 */
	public String getChannelName() {
		return mChannelName;
	}

	public void onPacketReceive(InetAddress pFrom, int pFromPort, Intent pData) {
		if (isPacket(pData, IA_CHANNEL)) {
			// channel broadcast
			Channel channel = new Channel();
			channel.setName(getIntentExtra(pData, IE_NAME));
			channel.setIP(getIntentExtra(pData, IE_IP));
			channel.setPort(Integer.valueOf(getIntentExtra(pData, IE_PORT)));
			channel.setPing(System.currentTimeMillis());

			synchronized (mChannels) {
				if (!mChannels.containsKey(channel.getChecksum())) {
					// new channel was found
					Log.d(LOG_TAG, "New channel '" + channel.getName()
							+ "' found!");
					mChannels.put(channel.getChecksum(), channel);
					if (mListener != null)
						mListener.onChannelFound(channel);
				}
				mChannels.put(channel.getChecksum(), channel);

				if (mListener != null)
					mListener.onChannelPing(channel);
			}

		}

		if (mHandler != null)
			mHandler.onPacketReceive(pFrom, pFromPort, pData);
	}

	@Override
	public void onServerCreated(ServerSocket pServer, int pPort) {
		// start sending broadcasts about the newly created channel/server
		Intent intent = new Intent(IA_CHANNEL);
		putIntentExtra(intent, IE_IP, getIP());
		putIntentExtra(intent, IE_PORT, pPort + "");
		putIntentExtra(intent, IE_NAME, mChannelName);
		sendPing(5000, intent);

		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {
				cancelPing();
			}
		}, 20000L);
	}

	@Override
	public void onClientJoined(Socket pClientSocket) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onClientLeft(Socket pClientSocket) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onClientTimedout(Socket pClientSocket) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onClientMessageReceived(Socket pClientSocket, Intent pData) {
		// TODO Auto-generated method stub

	}

	public ChannelListener getChannelListener() {
		return mListener;
	}

	public void setChannelListener(ChannelListener mListener) {
		this.mListener = mListener;
	}
}
